// 类型断言（Type Assertion）可以用来手动指定一个值的类型。
// 语法  ->   值 as 类型     或     <类型>值

// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候，我们只能访问此联合类型的所有类型中共有的属性或方法：
// 将一个联合类型断言为其中一个类型
// interface Cat {
//   name: string;
//   run(): void;
// }
// interface Fish {
//   name: string;
//   swim(): void;
// }
// function getName(animal: Cat | Fish) {
//   return animal.name
// }

// 此时可以使用类型断言，将 animal 断言成 Fish：
// interface Cat {
//   name: string;
//   run(): void;
// }
// interface Fish {
//   name: string;
//   swim(): void;
// }
// function getName(animal: Cat | Fish) {
//   (animal as Cat).run();
// }
// const tom: Cat = {
//   name: 'Tom',
//   run: function() {
//     console.log(this.name);
//   }
// }
// getName(tom)
// 上面的例子编译时不会报错，但在运行时会报错：
// 原因是 (animal as Fish).swim() 这段代码隐藏了 animal 可能为 Cat 的情况，将 animal 直接断言为 Fish 了，而 TypeScript 编译器信任了我们的断言，故在调用 swim() 时没有编译错误。
// 可是 swim 函数接受的参数是 Cat | Fish，一旦传入的参数是 Cat 类型的变量，由于 Cat 上没有 swim 方法，就会导致运行时错误了。
// 总之，使用类型断言时一定要格外小心，尽量避免断言后调用方法或引用深层属性，以减少不必要的运行时错误。

// 将一个父类断言为更加具体的子类
// 当类之间有继承关系时，类型断言也是很常见的：
class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}
// function isApiError(error: Error) {
//   if(typeof((error as ApiError).code) === 'number' ) return true
//   return false
// }
function isApiError(error: Error) {
    if (error instanceof ApiError) return true;
    return false;
}

// 但是有的情况下 ApiError 和 HttpError 不是一个真正的类，而只是一个 TypeScript 的接口（interface），
// 接口是一个类型，不是一个真正的值，它在编译结果中会被删除，当然就无法使用 instanceof 来做运行时判断了：
// interface ApiError extends Error {
//   code: number;
// }
// interface HttpError extends Error {
//   statusCode: number;
// }
// function isApiError(error: Error) {
//   if (error instanceof ApiError) {
//       return true;
//   }
//   return false;
// }
// 此时就只能用类型断言，通过判断是否存在 code 属性，来判断传入的参数是不是 ApiError 了：
// function isApiError(error: Error) {
//   if(typeof((error as ApiError).code) === 'number' ) return true
//   return false
// }

// 将任何一个类型断言为 any
// 当我们引用一个在此类型上不存在的属性或方法时，就会报错：
// const foo: number = 1;
// foo.length = 1; // error

// window.foo = 1 // error
// (window as any).foo = 1
// console.log(this.foo);

// 将 any 断言为一个具体的类型
// 那么我们在使用它时，最好能够将调用了它之后的返回值断言成一个精确的类型，这样就方便了后续的操作：
// const obj: any = {}
// function getCacheData(key: string): any {
//   return (obj as any).a = key;
// }
// interface Cat {
//   name: string;
//   run(): void;
// }
// const tom = getCacheData('tom') as Cat;
// // tom.run();
// console.log(obj);

// 类型断言的限制
// interface Animal {
//   name: string;
// }
// interface Cat {
//   name: string;
//   run(): void;
// }
// let tom: Cat = {
//   name: 'Tom',
//   run: () => { console.log('run') }
// };
// let animal: Animal = tom;
// console.log(animal);

// 当Animal兼容Cat时，他们就可以互相进行类型断言了：
interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

function testAnimal(animal: Animal) {
    return animal as Cat;
}
function testCat(cat: Cat) {
    return cat as Animal;
}
// 这样的设计其实也很容易就能理解：
// 允许animal as Cat 是因为父类可以被断言为子类，这个前面以及学习过了
// 允许cat as Animal 是因为 既然子类拥有父类的属性和方法，那么被断言为父类，获取父类的属性，调用父类的方法就不会有任何问题，故子类可以被断言为父类。
